package
{
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2DebugDraw;
	import Box2D.Dynamics.b2FixtureDef;
	import Box2D.Dynamics.b2World;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	[SWF(width="646", height="603", frameRate="30", backgroundColor="#333333")]
	/**
	 * Demonstration of Todd Kerpelman's puggle game updated to Box2D 2.1a.
	 * <p>Note: the SWF metatag frameRate should be also the value for STEP_SIMULATION_TIME.</p>
	 * */
	public class HelloWorldPuggle01 extends MovieClip
	{
		/**
		 * Tracking of all actors to call their methods.
		 * */
		private var _actors:Array;
		/**
		 * Tracking of all actors that need to be removed after each update of the Box2dWorld.
		 * */
		private var _actorsTaggedForRemoval:Array;
		/**
		 * Tracking of all peg actors that have been hit.
		 * */
		private var _pegsLitUp:Array;
		/**
		 * Tracking of all peg actors that are considered goal targets.
		 * */
		private var _goalPegs:Array;
		/**
		 * Shooter actor.
		 * */
		private var _shooter:Shooter;
		/**
		 * Total balls launched.
		 * */
		private var _totalBallsLaunced:int = 0;
		/**
		 * Maximum balls that can be in play at a time. 
		 * */
		private const MAX_LAUNCED_BALLS_ALLOWED:int = 2;
		/**
		 * Time to simulate world. Set Box2D world time step to movie frame rate. 
		 * Box2D manual recommends 1/60 seconds.
		 * */
		private const STEP_SIMULATION_TIME:Number = 1 / 30;
		/**
		 * Box2D interations for the velocity constraint solver
		 * */
		private const STEP_VELOCITY_ITERATIONS:int = 6;
		/**
		 * Box2D interations for the position constraint solver
		 * */
		private const POSITION_VELOCITY_ITERATIONS:int = 10;
		/**
		 * Horizontal spacing of PegActors.
		 * */
		private const PEG_H_SPACING:int = 36;
		/**
		 * Vertical spacing of PegActors.
		 * */
		private const PEG_V_SPACING:int = 36;
		/**
		 * Boundaries for the PegActors.
		 * */
		private const PEG_BOUNDARIES:Rectangle = new Rectangle(114, 226, 418, 255);
		/**
		 * Game side wall thickness.
		 * */
		private const SIDE_WALL_THICKNESS:Number = 10;
		/**
		 * Game side wall height.
		 * */
		private const SIDE_WALL_HEIGHT:Number = 603;
		/**
		 * Position of left wall.
		 * */
		private const LEFT_WALL_POSITION:Point = new Point(0,0);
		/**
		 * Position of right wall.
		 * */
		private const RIGHT_WALL_POSITION:Point = new Point(636,0);
		/**
		 * Left boundary of movable BonusChuteActor.
		 * */
		private const BONUS_CHUTE_LEFT_BOUNDS:int = 20;
		/**
		 * Right boundary of movable BonusChuteActor.
		 * */
		private const BONUS_CHUTE_RIGHT_BOUNDS:int = 433;
		/**
		 * Vertical position of the BonusChuteActor.
		 * */
		private const BONUS_CHUTE_VERTICAL_POSITION:int = 575;
		/**
		 * Position for the Shooter actor.
		 * */
		private const SHOOTER_POINT:Point = new Point(323,10);
		/**
		 * Velocity for the BallActors
		 * */
		private const LAUNCH_VELOCITY:Number = 90;
		/**
		 * Number of PegActors set as goal targets.
		 * */
		private const GOAL_PEG_NUM:int = 22;
		/**
		 * Developer testing for visualization of the Box2D elements using b2DebugDraw. 
		 * Determines if b2DebugDraw is added to stage and called.
		 * */
		private const SHOW_BOX2D_DEBUG:Boolean = false;
		/**
		 * Developer b2DebugDraw visual is shown on top of visual actors. False the 
		 * visual actors hide the b2DebugDraw visuals unless removed or are not occupying
		 * the intended position of the actors the Box2d world represents. Can be useful
		 * to see the removal process of actors and their Box2d counterparts when set to false.
		 * */
		private const SHOW_BOX2D_DEBUG_AS_OVERLAY:Boolean = true;
		/**
		 * Constructor. Setup world, actors, actor tracking variables and begin animation.
		 * */
		public function HelloWorldPuggle01():void 
		{
			// Initialize member variables.
			_actors = new Array();
			_actorsTaggedForRemoval = new Array();
			_pegsLitUp = new Array();
			_goalPegs = new Array();
			_shooter = new Shooter();
			// Add the Shooter actor
			addChild(_shooter);
			_shooter.x = SHOOTER_POINT.x;
			_shooter.y = SHOOTER_POINT.y;
			// Create the Box2d world.
			setupPhysicsWorld();
			// Create the game elements other than the Shooter.
			createLevel();
			// The ENTER_FRAME event will drive the updates to the Box2d world.
			addEventListener (Event.ENTER_FRAME, enterFrameHandler);
			// Handle the mouse clicks to the stage.
			stage.addEventListener(MouseEvent.CLICK, stageMouseClickHandler);
		}
		/**
		 * Creates the Box2dWorld
		 * */
		private function setupPhysicsWorld():void
		{
			// Create world. Gravity and let sleeping babies be.
			PhysiVals.world = new b2World(new b2Vec2(0,9.8), true);
			
			// Add b2DebugDraw under the visual actors.
			if (!SHOW_BOX2D_DEBUG_AS_OVERLAY)
			{
				addDebugDraw();
			}
			// Set the class to handle Box2d objects contacting each other.
			PhysiVals.world.SetContactListener(new PuggleContactListener());
		}
		/**
		 * b2DebugDraw visualization for Box2d. 
		 * */
		private function addDebugDraw():void
		{
			//Debugging visualization for Box2d. Will appear under the actors.
			if (SHOW_BOX2D_DEBUG)
			{
				var debugSprite:Sprite = new Sprite();
				addChild(debugSprite);
				var debugDraw:b2DebugDraw = new b2DebugDraw();
				debugDraw.SetSprite(debugSprite);
				debugDraw.SetDrawScale(PhysiVals.RATIO);
				debugDraw.SetLineThickness( 1.0);
				debugDraw.SetAlpha(1);
				debugDraw.SetFillAlpha(0.4);
				debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
				PhysiVals.world.SetDebugDraw(debugDraw);
			}
		}
		/**
		 * The only game level. Creates the walls, pegs, the ramps and the bonus chute.
		 * */
		private function createLevel():void
		{
			// Horizontal spacing of PegActors
			var hSpacing:int = PEG_H_SPACING;
			// Vertical spacing of PegActors
			var vSpacing:int = PEG_V_SPACING;
			// Rectangle boundaries of PegActors
			var pegBounds:Rectangle = PEG_BOUNDARIES;
			// Is odd numbered row
			var oddRow:Boolean = false;
			// Array of all PegActors created
			var allPegs:Array = new Array();
			// Loop from the top to the bottom of the PegActor boundaries until filled vertically.
			for (var pegY:int = pegBounds.top; pegY < pegBounds.bottom; pegY += vSpacing)
			{
				// The starting x position of the PegActor is 0 or half of horizonal spacing 
				// depending upon even or odd row state.
				var startX:int = pegBounds.left + ((oddRow) ? 0: (hSpacing / 2));
				// Flip odd row state.
				oddRow = ! oddRow;
				// Loop from the left to the right of the PegActor boundaries until filled horizontally.
				for (var pegX:int = startX; pegX < pegBounds.right; pegX += hSpacing)
				{
					// Create new PegActor in PegActor.NORMAL state
					var newPeg:PegActor = new PegActor(this, new Point (pegX, pegY), PegActor.NORMAL);
					// Add listener for PegEvent.PEG_LIT_UP.
					newPeg.addEventListener(PegEvent.PEG_LIT_UP, pegEventHandler);
					// Add listener for PegEvent.PEG_FADE_OUT_COMPLETE.
					newPeg.addEventListener(PegEvent.PEG_FADE_OUT_COMPLETE, pegEventHandler);
					// Add PegActor to actor tracking array.
					_actors.push(newPeg);
					// Add PegActor to local tracking array.
					allPegs.push(newPeg);
				}
			}
			// For the number of PegActors to set to PegActor.GOAL state.
			for (var i:int = 0; i < GOAL_PEG_NUM; i++)
			{
				// Select a PegActor randomly from local tracking array
				var randomPegNum:int = Math.floor(Math.random() * allPegs.length);
				// Set the PegActor to PegActor.GOAL state.
				PegActor(allPegs[randomPegNum]).setType(PegActor.GOAL);
				_goalPegs.push(allPegs[randomPegNum]);
				// Remove PegActor from local tracking array.
				allPegs.splice(randomPegNum, 1);
			}
			// Dimensions for the walls
			var wallShapeCoords:Array = new Array();
			wallShapeCoords.push(
				new Array(
					new Point(0,0), 
					new Point(SIDE_WALL_THICKNESS,0), 
					new Point(SIDE_WALL_THICKNESS,SIDE_WALL_HEIGHT), 
					new Point(0,SIDE_WALL_HEIGHT)
				)
			);
			//Create the left wall.
			var leftWall:ArbiStaticActor = new ArbiStaticActor(this, LEFT_WALL_POSITION, wallShapeCoords);
			_actors.push(leftWall);
			//Create the right wall.
			var rightWall:ArbiStaticActor = new ArbiStaticActor(this, RIGHT_WALL_POSITION, wallShapeCoords);
			_actors.push(rightWall);
			// Dimensions for the left ramps.
			var leftRampCoords:Array = new Array();
			leftRampCoords.push(
				new Array(
					new Point(0,0), 
					new Point(79,27), 
					new Point(79,30), 
					new Point(0,3)
					)
				);
			// Add left ramps
			var leftRamp1:ArbiStaticActor = new ArbiStaticActor(this, new Point(0,265), leftRampCoords);
			_actors.push(leftRamp1);
			var leftRamp2:ArbiStaticActor = new ArbiStaticActor(this, new Point(0,336), leftRampCoords);
			_actors.push(leftRamp2);
			var leftRamp3:ArbiStaticActor = new ArbiStaticActor(this, new Point(0,415), leftRampCoords);
			_actors.push(leftRamp3);
			// Dimensions for the right ramps
			var rightRampCoords:Array = new Array();
			rightRampCoords.push(
				new Array(
					new Point(0,0), 
					new Point(0, 3), 
					new Point(-85,32), 
					new Point(-85,29)
				)
			);
			// Add the right ramps
			var rightRamp1:ArbiStaticActor = new ArbiStaticActor(this, new Point(646, 232), rightRampCoords);
			_actors.push(rightRamp1);
			var rightRamp2:ArbiStaticActor = new ArbiStaticActor(this, new Point(646, 308), rightRampCoords);
			_actors.push(rightRamp2);
			var rightRamp3:ArbiStaticActor = new ArbiStaticActor(this, new Point(646, 388), rightRampCoords);
			_actors.push(rightRamp3);
			// Create the bonus chute
			var bonusChute:BonusChuteActor = new BonusChuteActor(this, 
				BONUS_CHUTE_LEFT_BOUNDS,
				BONUS_CHUTE_RIGHT_BOUNDS, 
				BONUS_CHUTE_VERTICAL_POSITION);
			_actors.push(bonusChute);
			// Add b2DebugDraw over the visual actors.
			if (SHOW_BOX2D_DEBUG_AS_OVERLAY)
			{
				addDebugDraw();
			}
		}
		/**
		 * Game ENTER_FRAME event handler.
		 * */
		private function enterFrameHandler(e:Event):void
		{
			updateWorld();
		}
		/**
		 * .
		 * */
		private function updateWorld():void
		{
			// Step simulation time
			var timeStep:Number = STEP_SIMULATION_TIME;
			// Step velocity iterations
			var velocityIterations:int = STEP_VELOCITY_ITERATIONS;
			// Step position interactions
			var positionIterations:int = POSITION_VELOCITY_ITERATIONS;
			//Collision detection, integration, and constraint solutions performed.
			PhysiVals.world.Step( timeStep, velocityIterations, positionIterations);
			// As of version 2.1 we must clear the forces.
			PhysiVals.world.ClearForces();
			// You want to show debug visualizations.
			if (SHOW_BOX2D_DEBUG)
			{
				PhysiVals.world.DrawDebugData();
			}
			// Update all the Actors.
			for each (var actor:Actor in _actors)
			{
				actor.updateNow();
			}
			// Remove actors tagged for removal.
			removeActors();
		}
		/**
		 * Marks an actor for removal
		 * */
		public function markActorForRemoval(actor:Actor):void
		{
			if (_actorsTaggedForRemoval.indexOf(actor) < 0 )
			{
				_actorsTaggedForRemoval.push(actor);
			}
		}
		/**
		 * Remove actors. Calls their destroy methods.
		 * */
		private function removeActors():void
		{
			// Each Actor tagged for removal.
			for each (var actor:Actor in _actorsTaggedForRemoval)
			{
				// This is a BallActor.
				if (actor is BallActor)
				{
					// Reduce counter for number of BallActors in play.
					_totalBallsLaunced --;
				}
				// Destroy the actor using its destroy() method.
				actor.destroy();
				// Remove Actor from the actors tracking array.
				var actorIndex:int = _actors.indexOf(actor);
				if (actorIndex > -1)
				{
					_actors.splice(actorIndex, 1);
				}
			}
			// Reset the actors tagged for removal tracking array.
			_actorsTaggedForRemoval = new Array();
		}
		/**
		 * Handler for stage MouseEvent.CLICK.
		 * */
		private function stageMouseClickHandler(e:MouseEvent):void
		{
			launchBall();
		}
		/**
		 * Launch a new ball.
		 * */
		private function launchBall():void
		{
			
			if (_totalBallsLaunced < MAX_LAUNCED_BALLS_ALLOWED)
			{
				// Get the launch position from the Shooter.
				var launchPoint:Point = _shooter.getLaunchPosition();
				// Subtract the coordinates of launch point from the mouse point to get direction.
				var direction:Point = new Point(mouseX,mouseY).subtract(launchPoint);
				// Scale by the LAUNCH_VELOCITY.
				direction.normalize(LAUNCH_VELOCITY);
				
				// Create a BallActor
				var ballActor:BallActor = new BallActor(this, launchPoint, direction);
				ballActor.addEventListener(BallEvent.BALL_OFF_SCREEN, ballOffScreenEventHandler);
				ballActor.addEventListener(BallEvent.BALL_HIT_BONUS, ballHitBonusEventHandler);
				_actors.push(ballActor);
				_totalBallsLaunced++
			}
		}
		/**
		 * Handler for BallEvent.BALL_HIT_BONUS
		 * */
		private function ballHitBonusEventHandler(e:BallEvent):void
		{
			var ballActor:BallActor = BallActor(e.currentTarget);
			markActorForRemoval(ballActor);
			removePegActors();
			ballActor.removeEventListener(BallEvent.BALL_HIT_BONUS,ballHitBonusEventHandler);
		}
		/**
		 * Handler for BallEvent.BALL_OFF_SCREEN
		 * */		
		private function ballOffScreenEventHandler(e:BallEvent):void
		{
			var ballActor:BallActor = BallActor(e.currentTarget);
			markActorForRemoval(ballActor);
			removePegActors();
			ballActor.removeEventListener(BallEvent.BALL_OFF_SCREEN,ballOffScreenEventHandler);
		}
		/**
		 * Fade out peg actors that have been hit (lit up). Removed from the array of lit up PegActors
		 * and the fade out eventually leads to their removal.
		 * */		
		private function removePegActors():void
		{
			var pegNumber:int = 0;
			for each (var pegActor:PegActor in _pegsLitUp)
			{
				pegActor.fadeOut(pegNumber);
				pegNumber++;
			}
			_pegsLitUp = new Array();
		}
		/**
		 * Handler for the PegActor PegEvent.PEG_LIT_UP and PegEvent.PEG_FADE_OUT_COMPLETE events.
		 * */		
		private function pegEventHandler(e:PegEvent):void
		{
			switch (e.type)
			{
				case PegEvent.PEG_LIT_UP:
					pegLitUpState(PegActor(e.currentTarget));
					break;
				case PegEvent.PEG_FADE_OUT_COMPLETE:
					pegFadeOutState(PegActor(e.currentTarget));
					break;
			}
		}
		/**
		 * Add tracking for PegActors that are lit up.
		 * */		
		private function pegLitUpState(pegActor:PegActor):void
		{
			pegActor.removeEventListener(PegEvent.PEG_LIT_UP,pegEventHandler);
			if (_pegsLitUp.indexOf(pegActor) < 0 )
			{
				_pegsLitUp.push(pegActor);
			}
		}
		/**
		 * Tag PegActors for removal if they have faded out.
		 * */		
		private function pegFadeOutState(pegActor:PegActor):void
		{
			_actorsTaggedForRemoval.push(pegActor);
		}
	}
}